New tooltips API.
authorKristian Rietveld <kris@imendio.com>
Tue, 6 Feb 2007 10:25:21 +0000 (10:25 +0000)
committerKristian Rietveld <kristian@src.gnome.org>
Tue, 6 Feb 2007 10:25:21 +0000 (10:25 +0000)
2007-02-06  Kristian Rietveld  <kris@imendio.com>

New tooltips API.

* gtk/Makefile.am
* gtk/gtk.h
* gtk/gtk.symbols: build system foo.

* gtk/gtkmain.c (gtk_main_do_event): call tooltip event handler
hook for appropriate events.

* gtk/gtkmarshalers.list: add BOOLEAN:INT,INT,BOOLEAN,OBJECT.

* gtk/gtkrc.c: add style for gtk-tooltip.

* gtk/gtksettings.c (gtk_settings_class_init): make the
different tooltip timeouts configurable.

* gtk/gtkwidget.[ch]: add new properties, signals, make sure
tooltips are hidden on unmap, destroy, update window event
mask on realize, hook into focus change and show help
handlers.

* gtk/gtkwindow.h: move GtkWindow typdef to gtkwidget.h ...

* gtk/gtktooltip.[ch]: new files.

* tests/Makefile.am
* tests/testtooltips.c: add test application.

svn path=/trunk/; revision=17264

15 files changed:
ChangeLog
gtk/Makefile.am
gtk/gtk.h
gtk/gtk.symbols
gtk/gtkmain.c
gtk/gtkmarshalers.list
gtk/gtkrc.c
gtk/gtksettings.c
gtk/gtktooltip.c [new file with mode: 0644]
gtk/gtktooltip.h [new file with mode: 0644]
gtk/gtkwidget.c
gtk/gtkwidget.h
gtk/gtkwindow.h
tests/Makefile.am
tests/testtooltips.c [new file with mode: 0644]

index ee01fe0424bf4befac522c4ccead44785ec09dbc..0ff9c9aadf89bc51721b51c3ac09cb17738cd183 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+2007-02-06  Kristian Rietveld  <kris@imendio.com>
+
+       New tooltips API.
+
+       * gtk/Makefile.am
+       * gtk/gtk.h
+       * gtk/gtk.symbols: build system foo.
+
+       * gtk/gtkmain.c (gtk_main_do_event): call tooltip event handler
+       hook for appropriate events.
+
+       * gtk/gtkmarshalers.list: add BOOLEAN:INT,INT,BOOLEAN,OBJECT.
+
+       * gtk/gtkrc.c: add style for gtk-tooltip.
+
+       * gtk/gtksettings.c (gtk_settings_class_init): make the
+       different tooltip timeouts configurable.
+
+       * gtk/gtkwidget.[ch]: add new properties, signals, make sure
+       tooltips are hidden on unmap, destroy, update window event
+       mask on realize, hook into focus change and show help
+       handlers.
+
+       * gtk/gtkwindow.h: move GtkWindow typdef to gtkwidget.h ...
+
+       * gtk/gtktooltip.[ch]: new files.
+
+       * tests/Makefile.am
+       * tests/testtooltips.c: add test application.
+
 2007-02-05  Dom Lachowicz <domlachowicz@gmail.com>
 
        * modules/engines/ms-windows/msw_style.c: Fix bug 404506, caused
index 749b7ff8f59aefd8a582d1690793922a0f6868e7..b212d62ed784ba2d585d3de0497c1f5b88369eee 100644 (file)
@@ -292,6 +292,7 @@ gtk_public_h_sources =          \
        gtktoolbar.h            \
        gtktoolbutton.h         \
        gtktoolitem.h           \
+       gtktooltip.h            \
        gtktooltips.h           \
        gtktree.h               \
        gtktreednd.h            \
@@ -559,6 +560,7 @@ gtk_base_c_sources =            \
        gtktoolbar.c            \
        gtktoolbutton.c         \
        gtktoolitem.c           \
+       gtktooltip.c            \
        gtktooltips.c           \
        gtktree.c               \
        gtktreedatalist.c       \
index f4f927eda15185f20ae22f2e6ac3f723a14323b6..487f5eae1395185a9887a0562ba7e1d602bb9786 100644 (file)
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
 #include <gtk/gtktoolbar.h>
 #include <gtk/gtktoolbutton.h>
 #include <gtk/gtktoolitem.h>
+#include <gtk/gtktooltip.h>
 #include <gtk/gtktooltips.h>
 #include <gtk/gtktree.h>
 #include <gtk/gtktreednd.h>
index cabc850febdad5e2d2261be4f8b37332b094482a..1eedabd27b1ae20fa3499d63815a4b038800f950 100644 (file)
@@ -3961,6 +3961,17 @@ gtk_tool_item_set_visible_vertical
 #endif
 #endif
 
+#if IN_HEADER(__GTK_TOOLTIP_H__)
+#if IN_FILE(__GTK_TOOLTIP_C__)
+gtk_tooltip_get_type G_GNUC_CONST
+gtk_tooltip_set_custom
+gtk_tooltip_set_icon
+gtk_tooltip_set_icon_from_stock
+gtk_tooltip_set_markup
+gtk_tooltip_trigger_tooltip_query
+#endif
+#endif
+
 #if IN_HEADER(__GTK_TOOLTIPS_H__)
 #if IN_FILE(__GTK_TOOLTIPS_C__)
 gtk_tooltips_data_get
@@ -4508,6 +4519,7 @@ gtk_widget_get_screen
 gtk_widget_get_settings
 gtk_widget_get_size_request
 gtk_widget_get_style
+gtk_widget_get_tooltip_window
 gtk_widget_get_toplevel
 gtk_widget_get_type G_GNUC_CONST
 gtk_widget_get_visual
@@ -4580,6 +4592,7 @@ gtk_widget_set_sensitive
 gtk_widget_set_size_request
 gtk_widget_set_state
 gtk_widget_set_style
+gtk_widget_set_tooltip_window
 gtk_widget_shape_combine_mask
 gtk_widget_input_shape_combine_mask
 gtk_widget_show
@@ -4592,6 +4605,7 @@ gtk_widget_style_get_property
 gtk_widget_style_get_valist
 gtk_widget_thaw_child_notify
 gtk_widget_translate_coordinates
+gtk_widget_trigger_tooltip_query
 gtk_widget_unmap
 gtk_widget_unparent
 gtk_widget_unrealize
index cbf393bf40587883d72782e9f293866c2af7fd86..2480f72f35d5545443160bac453e20a1414a161d 100644 (file)
@@ -65,6 +65,7 @@
 #include "gtksettings.h"
 #include "gtkwidget.h"
 #include "gtkwindow.h"
+#include "gtktooltip.h"
 #include "gtkprivate.h"
 #include "gtkdebug.h"
 #include "gtkalias.h"
@@ -1593,6 +1594,20 @@ gtk_main_do_event (GdkEvent *event)
       g_assert_not_reached ();
       break;
     }
+
+  if (event->type == GDK_ENTER_NOTIFY
+      || event->type == GDK_LEAVE_NOTIFY
+      || event->type == GDK_BUTTON_PRESS
+      || event->type == GDK_2BUTTON_PRESS
+      || event->type == GDK_3BUTTON_PRESS
+      || event->type == GDK_KEY_PRESS
+      || event->type == GDK_DRAG_ENTER
+      || event->type == GDK_GRAB_BROKEN
+      || event->type == GDK_MOTION_NOTIFY
+      || event->type == GDK_SCROLL)
+    {
+      _gtk_tooltip_handle_event (event);
+    }
   
   tmp_list = current_events;
   current_events = g_list_remove_link (current_events, tmp_list);
index 175c14949748d392185715e0947813983390a94a..211bca5663b00bba645917833f45e231b8cf7846 100644 (file)
@@ -39,6 +39,7 @@ BOOLEAN:OBJECT,ENUM
 BOOLEAN:INT
 BOOLEAN:INT,INT
 BOOLEAN:INT,INT,INT
+BOOLEAN:INT,INT,BOOLEAN,OBJECT
 BOOLEAN:UINT
 BOOLEAN:VOID
 BOOLEAN:BOOLEAN
index 96c68cbce1d69f79516d7b4a6ff87a629c2c57f9..2ba6f3687a0f879be8ef53774909e85405103a94 100644 (file)
@@ -893,7 +893,7 @@ _gtk_rc_init (void)
                        "\n"    
                       "class \"GtkProgressBar\" style : gtk \"gtk-default-progress-bar-style\"\n"
                       "class \"GtkTrayIcon\" style : gtk \"gtk-default-tray-icon-style\"\n"
-                      "widget \"gtk-tooltips*\" style : gtk \"gtk-default-tooltips-style\"\n"
+                      "widget \"gtk-tooltip*\" style : gtk \"gtk-default-tooltips-style\"\n"
                       "widget_class \"*<GtkMenuItem>*\" style : gtk \"gtk-default-menu-item-style\"\n"
                       "widget_class \"*<GtkMenuBar>*<GtkMenuItem>\" style : gtk \"gtk-default-menu-bar-item-style\"\n"
                        "class \"GtkLabel\" style : gtk \"gtk-default-label-style\"\n"
index fb7858fbe5bf2203f01531d347f6f80ab0e18a42..8fdb4598612f9a2a45eeba19feeba032ad7bc4aa 100644 (file)
@@ -94,6 +94,9 @@ enum {
   PROP_COLOR_SCHEME,
   PROP_ENABLE_ANIMATIONS,
   PROP_TOUCHSCREEN_MODE,
+  PROP_TOOLTIP_TIMEOUT,
+  PROP_TOOLTIP_BROWSE_TIMEOUT,
+  PROP_TOOLTIP_BROWSE_MODE_TIMEOUT,
   PROP_KEYNAV_CURSOR_ONLY,
   PROP_KEYNAV_WRAP_AROUND,
   PROP_ERROR_BELL,
@@ -556,6 +559,73 @@ gtk_settings_class_init (GtkSettingsClass *class)
 
   g_assert (result == PROP_TOUCHSCREEN_MODE);
 
+  /**
+   * GtkSettings:gtk-tooltip-timeout:
+   *
+   * Time, in milliseconds, after which a tooltip could appear if the
+   * cursor is hovering on top of a widget.
+   *
+   * Since: 2.12
+   */
+  result = settings_install_property_parser (class,
+                                            g_param_spec_int ("gtk-tooltip-timeout",
+                                                              P_("Tooltip timeout"),
+                                                              P_("Timeout before tooltip is shown"),
+                                                              0, G_MAXINT,
+                                                              1500,
+                                                              GTK_PARAM_READWRITE),
+                                            NULL);
+
+  g_assert (result == PROP_TOOLTIP_TIMEOUT);
+
+  /**
+   * GtkSettings:gtk-tooltip-browse-timeout:
+   *
+   * Controls the time after which tooltips will appear when
+   * browse mode is enabled, in milliseconds.
+   *
+   * Browse mode is enabled when the mouse pointer moves off an object
+   * where a tooltip was currently being displayed.  If the mouse pointer
+   * hits another object before the browse mode timeout expires (see
+   * gtk-tooltip-browse-mode-timeout), it will take the amount of
+   * milliseconds specified by this setting to popup the tooltip
+   * for the new object.
+   *
+   * Since: 2.12
+   */
+  result = settings_install_property_parser (class,
+                                            g_param_spec_int ("gtk-tooltip-browse-timeout",
+                                                              P_("Tooltip browse timeout"),
+                                                              P_("Timeout before tooltip is shown when browse mode is enabled"),
+                                                              0, G_MAXINT,
+                                                              100,
+                                                              GTK_PARAM_READWRITE),
+                                            NULL);
+
+  g_assert (result == PROP_TOOLTIP_BROWSE_TIMEOUT);
+
+  /**
+   * GtkSettings:gtk-tooltip-browse-mode-timeout:
+   *
+   * Amount of time, in milliseconds, after which the browse mode
+   * will be disabled.
+   *
+   * See GtkSettings:gtk-tooltip-browse-timeout for more information
+   * about browse mode.
+   *
+   * Since: 2.12
+   */
+  result = settings_install_property_parser (class,
+                                            g_param_spec_int ("gtk-tooltip-browse-mode-timeout",
+                                                              P_("Tooltip browse mode timeout"),
+                                                              P_("Timeout after which browse mode is disabled"),
+                                                              0, G_MAXINT,
+                                                              500,
+                                                              GTK_PARAM_READWRITE),
+                                            NULL);
+
+  g_assert (result == PROP_TOOLTIP_BROWSE_MODE_TIMEOUT);
+
   /**
    * GtkSettings:gtk-keynav-cursor-only:
    *
diff --git a/gtk/gtktooltip.c b/gtk/gtktooltip.c
new file mode 100644 (file)
index 0000000..2e6f69a
--- /dev/null
@@ -0,0 +1,1064 @@
+/* gtktooltip.c
+ *
+ * Copyright (C) 2006-2007 Imendio AB
+ * Contact: Kristian Rietveld <kris@imendio.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gtktooltip.h"
+#include "gtkintl.h"
+#include "gtkwindow.h"
+#include "gtkmain.h"
+#include "gtklabel.h"
+#include "gtkimage.h"
+#include "gtkhbox.h"
+#include "gtkalignment.h"
+
+#include <string.h>
+
+
+#define GTK_TOOLTIP(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TOOLTIP, GtkTooltip))
+#define GTK_TOOLTIP_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TOOLTIP, GtkTooltipClass))
+#define GTK_IS_TOOLTIP(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TOOLTIP))
+#define GTK_IS_TOOLTIP_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TOOLTIP))
+#define GTK_TOOLTIP_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TOOLTIP, GtkTooltipClass))
+
+typedef struct _GtkTooltipClass   GtkTooltipClass;
+
+struct _GtkTooltip
+{
+  GObject parent_instance;
+
+  GtkWidget *window;
+  GtkWidget *alignment;
+  GtkWidget *box;
+  GtkWidget *image;
+  GtkWidget *label;
+  GtkWidget *custom_widget;
+
+  GtkWindow *current_window;
+  GtkWidget *keyboard_widget;
+
+  GtkWidget *tooltip_widget;
+  GdkWindow *toplevel_window;
+
+  gdouble last_x;
+  gdouble last_y;
+  GdkWindow *last_window;
+
+  guint timeout_id;
+  guint browse_mode_timeout_id;
+
+  guint browse_mode_enabled : 1;
+  guint keyboard_mode_enabled : 1;
+};
+
+struct _GtkTooltipClass
+{
+  GObjectClass parent_class;
+};
+
+#define GTK_TOOLTIP_VISIBLE(tooltip) ((tooltip)->current_window && GTK_WIDGET_VISIBLE ((tooltip)->current_window))
+
+
+static void       gtk_tooltip_class_init           (GtkTooltipClass *klass);
+static void       gtk_tooltip_init                 (GtkTooltip      *tooltip);
+static void       gtk_tooltip_finalize             (GObject         *object);
+
+static gboolean   gtk_tooltip_paint_window         (GtkTooltip      *tooltip);
+static void       gtk_tooltip_window_hide          (GtkWidget       *widget,
+                                                   gpointer         user_data);
+static void       gtk_tooltip_display_closed       (GdkDisplay      *display,
+                                                   gboolean         was_error,
+                                                   GtkTooltip      *tooltip);
+
+
+G_DEFINE_TYPE (GtkTooltip, gtk_tooltip, G_TYPE_OBJECT);
+
+static void
+gtk_tooltip_class_init (GtkTooltipClass *klass)
+{
+  GObjectClass *object_class;
+
+  object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_tooltip_finalize;
+}
+
+static void
+gtk_tooltip_init (GtkTooltip *tooltip)
+{
+  tooltip->timeout_id = 0;
+  tooltip->browse_mode_timeout_id = 0;
+
+  tooltip->browse_mode_enabled = FALSE;
+  tooltip->keyboard_mode_enabled = FALSE;
+
+  tooltip->current_window = NULL;
+  tooltip->keyboard_widget = NULL;
+
+  tooltip->tooltip_widget = NULL;
+  tooltip->toplevel_window = NULL;
+
+  tooltip->last_window = NULL;
+
+  tooltip->window = g_object_ref (gtk_window_new (GTK_WINDOW_POPUP));
+  gtk_window_set_type_hint (GTK_WINDOW (tooltip->window),
+                           GDK_WINDOW_TYPE_HINT_TOOLTIP);
+  gtk_widget_set_app_paintable (tooltip->window, TRUE);
+  gtk_window_set_resizable (GTK_WINDOW (tooltip->window), FALSE);
+  gtk_widget_set_name (tooltip->window, "gtk-tooltip");
+  g_signal_connect (tooltip->window, "hide",
+                   G_CALLBACK (gtk_tooltip_window_hide), tooltip);
+
+  tooltip->alignment = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
+  gtk_alignment_set_padding (GTK_ALIGNMENT (tooltip->alignment),
+                            tooltip->window->style->ythickness,
+                            tooltip->window->style->ythickness,
+                            tooltip->window->style->xthickness,
+                            tooltip->window->style->xthickness);
+  gtk_container_add (GTK_CONTAINER (tooltip->window), tooltip->alignment);
+  gtk_widget_show (tooltip->alignment);
+
+  g_signal_connect_swapped (tooltip->window, "expose_event",
+                           G_CALLBACK (gtk_tooltip_paint_window), tooltip);
+
+  tooltip->box = gtk_hbox_new (FALSE, tooltip->window->style->xthickness);
+  gtk_container_add (GTK_CONTAINER (tooltip->alignment), tooltip->box);
+  gtk_widget_show (tooltip->box);
+
+  tooltip->image = gtk_image_new ();
+  gtk_box_pack_start (GTK_BOX (tooltip->box), tooltip->image,
+                     FALSE, FALSE, 0);
+
+  tooltip->label = gtk_label_new ("");
+  gtk_box_pack_start (GTK_BOX (tooltip->box), tooltip->label,
+                     FALSE, FALSE, 0);
+
+  tooltip->custom_widget = NULL;
+}
+
+static void
+gtk_tooltip_finalize (GObject *object)
+{
+  GtkTooltip *tooltip = GTK_TOOLTIP (object);
+
+  if (tooltip->timeout_id)
+    {
+      g_source_remove (tooltip->timeout_id);
+      tooltip->timeout_id = 0;
+    }
+
+  if (tooltip->browse_mode_timeout_id)
+    {
+      g_source_remove (tooltip->browse_mode_timeout_id);
+      tooltip->browse_mode_timeout_id = 0;
+    }
+
+  if (tooltip->window)
+    {
+      GdkDisplay *display;
+
+      display = gtk_widget_get_display (tooltip->window);
+      g_signal_handlers_disconnect_by_func (display,
+                                           gtk_tooltip_display_closed,
+                                           tooltip);
+      gtk_widget_destroy (tooltip->window);
+    }
+}
+
+/* public API */
+
+/**
+ * gtk_tooltip_set_markup:
+ * @label: a #GtkTooltip
+ * @markup: a markup string (see <link linkend="PangoMarkupFormat">Pango markup format</link>) or %NULL
+ *
+ * Sets the text of the tooltip to be @str, which is marked up
+ * with the <link
+ * linkend="PangoMarkupFormat">Pango text markup language</link>.
+ * If @markup is %NULL, the label will be hidden.
+ *
+ * Since: 2.12
+ */
+void
+gtk_tooltip_set_markup (GtkTooltip  *tooltip,
+                       const gchar *markup)
+{
+  g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
+
+  gtk_label_set_markup (GTK_LABEL (tooltip->label), markup);
+
+  if (markup)
+    gtk_widget_show (tooltip->label);
+  else
+    gtk_widget_hide (tooltip->label);
+}
+
+/**
+ * gtk_tooltip_set_icon:
+ * @tooltip: a #GtkTooltip
+ * @pixbuf: a #GdkPixbuf, or %NULL
+ *
+ * Sets the icon of the tooltip (which is in front of the text) to be
+ * @pixbuf.  If @pixbuf is %NULL, the image will be hidden.
+ *
+ * Since: 2.12
+ */
+void
+gtk_tooltip_set_icon (GtkTooltip *tooltip,
+                     GdkPixbuf  *pixbuf)
+{
+  g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
+  if (pixbuf)
+    g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
+
+  gtk_image_set_from_pixbuf (GTK_IMAGE (tooltip->image), pixbuf);
+
+  if (pixbuf)
+    gtk_widget_show (tooltip->image);
+  else
+    gtk_widget_hide (tooltip->image);
+}
+
+/**
+ * gtk_tooltip_set_icon_from_stock:
+ * @tooltip: a #GtkTooltip
+ * @stock_id: a stock icon name, or %NULL
+ * @size: a stock icon size
+ *
+ * Sets the icon of the tooltip (which is in front of the text) to be
+ * the stock item indicated by @stock_id with the size indicated
+ * by @size.  If @stock_id is %NULL, the image will be hidden.
+ *
+ * Since: 2.12
+ */
+void
+gtk_tooltip_set_icon_from_stock (GtkTooltip  *tooltip,
+                                const gchar *stock_id,
+                                GtkIconSize  size)
+{
+  g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
+
+  gtk_image_set_from_stock (GTK_IMAGE (tooltip->image), stock_id, size);
+
+  if (stock_id)
+    gtk_widget_show (tooltip->image);
+  else
+    gtk_widget_hide (tooltip->image);
+}
+
+/**
+ * gtk_tooltip_set_custom:
+ * tooltip: a #GtkTooltip
+ * custom_widget: a #GtkWidget
+ *
+ * Replaces the widget packed into the tooltip with @custom_widget.  By
+ * default a box with a #GtkImage and #GtkLabel is embedded in the tooltip,
+ * which can be configured using gtk_tooltip_set_markup() and
+ * gtk_tooltip_set_icon().
+ *
+ * Since: 2.12
+ */
+void
+gtk_tooltip_set_custom (GtkTooltip *tooltip,
+                       GtkWidget  *custom_widget)
+{
+  g_return_if_fail (GTK_IS_TOOLTIP (tooltip));
+  if (custom_widget)
+    g_return_if_fail (GTK_IS_WIDGET (custom_widget));
+
+  if (tooltip->custom_widget)
+    {
+      gtk_container_remove (GTK_CONTAINER (tooltip->box),
+                           tooltip->custom_widget);
+      g_object_unref (tooltip->custom_widget);
+    }
+
+  if (custom_widget)
+    {
+      tooltip->custom_widget = g_object_ref (custom_widget);
+
+      gtk_container_add (GTK_CONTAINER (tooltip->box), custom_widget);
+      gtk_widget_show (custom_widget);
+    }
+  else
+    tooltip->custom_widget = NULL;
+}
+
+/**
+ * gtk_tooltip_trigger_tooltip_query:
+ * @display: a #GtkDisplay
+ *
+ * Triggers a new tooltip query on @display, in order to update the current
+ * visible tooltip, or to show/hide the current tooltip.  This function is
+ * useful to call when, for example, the state of the widget changed by a
+ * key press.
+ *
+ * Since: 2.12
+ */
+void
+gtk_tooltip_trigger_tooltip_query (GdkDisplay *display)
+{
+  gint x, y;
+  GdkWindow *window;
+  GdkEvent event;
+
+  /* Trigger logic as if the mouse moved */
+  window = gdk_display_get_window_at_pointer (display, &x, &y);
+  if (!window)
+    return;
+
+  event.type = GDK_MOTION_NOTIFY;
+  event.motion.window = window;
+  event.motion.x = x;
+  event.motion.y = y;
+  event.motion.is_hint = FALSE;
+
+  _gtk_tooltip_handle_event (&event);
+}
+
+/* private functions */
+
+static void
+gtk_tooltip_reset (GtkTooltip *tooltip)
+{
+  gtk_tooltip_set_markup (tooltip, NULL);
+  gtk_tooltip_set_icon (tooltip, NULL);
+  gtk_tooltip_set_custom (tooltip, NULL);
+}
+
+static gboolean
+gtk_tooltip_paint_window (GtkTooltip *tooltip)
+{
+  GtkRequisition req;
+
+  gtk_widget_size_request (tooltip->window, &req);
+  gtk_paint_flat_box (tooltip->window->style,
+                     tooltip->window->window,
+                     GTK_STATE_NORMAL,
+                     GTK_SHADOW_OUT,
+                     NULL,
+                     tooltip->window,
+                     "tooltip",
+                     0, 0,
+                     tooltip->window->allocation.width,
+                     tooltip->window->allocation.height);
+
+  return FALSE;
+}
+
+static void
+gtk_tooltip_window_hide (GtkWidget *widget,
+                        gpointer   user_data)
+{
+  GtkTooltip *tooltip = GTK_TOOLTIP (user_data);
+
+  if (tooltip->custom_widget)
+    gtk_tooltip_set_custom (tooltip, NULL);
+}
+
+/* event handling, etc */
+
+struct ChildLocation
+{
+  GtkWidget *child;
+  GtkWidget *container;
+
+  gint x;
+  gint y;
+};
+
+static void
+child_location_foreach (GtkWidget *child,
+                       gpointer   data)
+{
+  struct ChildLocation *child_loc = data;
+
+  if (!child_loc->child)
+    {
+      gint x, y;
+
+      gtk_widget_translate_coordinates (child_loc->container, child,
+                                       child_loc->x, child_loc->y,
+                                       &x, &y);
+
+      if (x >= 0 && x < child->allocation.width
+         && y >= 0 && y < child->allocation.height)
+        {
+         if (GTK_IS_CONTAINER (child))
+           {
+             struct ChildLocation tmp = { NULL, NULL, 0, 0 };
+
+             tmp.x = x;
+             tmp.y = y;
+             tmp.container = child;
+
+             gtk_container_foreach (GTK_CONTAINER (child),
+                                    child_location_foreach, &tmp);
+
+             if (tmp.child)
+               child_loc->child = tmp.child;
+             else
+               child_loc->child = child;
+           }
+         else
+           child_loc->child = child;
+       }
+    }
+}
+
+static void
+window_to_alloc (GtkWidget *dest_widget,
+                gint       src_x,
+                gint       src_y,
+                gint      *dest_x,
+                gint      *dest_y)
+{
+  /* Translate from window relative to allocation relative */
+  if (!GTK_WIDGET_NO_WINDOW (dest_widget) && dest_widget->parent)
+    {
+      gint wx, wy;
+      gdk_window_get_position (dest_widget->window, &wx, &wy);
+
+      src_x += wx - dest_widget->allocation.x;
+      src_y += wy - dest_widget->allocation.y;
+    }
+  else
+    {
+      src_x -= dest_widget->allocation.x;
+      src_y -= dest_widget->allocation.y;
+    }
+
+  if (dest_x)
+    *dest_x = src_x;
+  if (dest_y)
+    *dest_y = src_y;
+}
+
+static GtkWidget *
+find_widget_under_pointer (GdkWindow *window,
+                          gint      *x,
+                          gint      *y)
+{
+  GtkWidget *event_widget;
+  struct ChildLocation child_loc = { NULL, NULL, 0, 0 };
+
+  child_loc.x = *x;
+  child_loc.y = *y;
+
+  gdk_window_get_user_data (window, (void **)&event_widget);
+  if (GTK_IS_CONTAINER (event_widget))
+    {
+      window_to_alloc (event_widget,
+                      child_loc.x, child_loc.y,
+                      &child_loc.x, &child_loc.y);
+
+      child_loc.container = event_widget;
+      child_loc.child = NULL;
+
+      gtk_container_foreach (GTK_CONTAINER (event_widget),
+                            child_location_foreach, &child_loc);
+
+      if (child_loc.child)
+       event_widget = child_loc.child;
+      else if (child_loc.container)
+       event_widget = child_loc.container;
+    }
+
+  if (x)
+    *x = child_loc.x;
+  if (y)
+    *y = child_loc.y;
+
+  return event_widget;
+}
+
+static GtkWidget *
+find_topmost_widget_coords_from_event (GdkEvent *event,
+                                      gint     *x,
+                                      gint     *y)
+{
+  gint tx, ty;
+  gdouble dx, dy;
+  GtkWidget *tmp;
+  GtkWidget *orig;
+  gboolean has_tooltip;
+
+  gdk_event_get_coords (event, &dx, &dy);
+  tx = dx;
+  ty = dy;
+
+  orig = tmp = find_widget_under_pointer (event->any.window, &tx, &ty);
+
+  g_object_get (tmp, "has-tooltip", &has_tooltip, NULL);
+
+  if (tmp && (x != NULL || y != NULL))
+    {
+      if (tmp != orig)
+       gtk_widget_translate_coordinates (orig, tmp, tx, ty, x, y);
+      else
+        {
+         if (x)
+           *x = tx;
+         if (y)
+           *y = ty;
+       }
+    }
+
+  return tmp;
+}
+
+static gint
+tooltip_browse_mode_expired (gpointer data)
+{
+  GtkTooltip *tooltip;
+
+  GDK_THREADS_ENTER ();
+
+  tooltip = GTK_TOOLTIP (data);
+
+  tooltip->browse_mode_enabled = FALSE;
+  tooltip->browse_mode_timeout_id = 0;
+
+  /* destroy tooltip */
+  g_object_set_data (G_OBJECT (gtk_widget_get_display (tooltip->window)),
+                    "gdk-display-current-tooltip", NULL);
+
+  GDK_THREADS_LEAVE ();
+
+  return FALSE;
+}
+
+static void
+gtk_tooltip_display_closed (GdkDisplay *display,
+                           gboolean    was_error,
+                           GtkTooltip *tooltip)
+{
+  g_object_set (display, "gdk-display-current-tooltip", NULL);
+}
+
+static gboolean
+gtk_tooltip_run_requery (GtkWidget  **widget,
+                        GtkTooltip  *tooltip,
+                        gint        *x,
+                        gint        *y)
+{
+  gboolean has_tooltip = FALSE;
+  gboolean return_value = FALSE;
+
+  gtk_tooltip_reset (tooltip);
+
+  do
+    {
+      g_object_get (*widget,
+                   "has-tooltip", &has_tooltip,
+                   NULL);
+
+      if (has_tooltip)
+       g_signal_emit_by_name (*widget,
+                              "query-tooltip",
+                              *x, *y,
+                              tooltip->keyboard_mode_enabled,
+                              tooltip,
+                              &return_value);
+
+      if (!return_value)
+        {
+         GtkWidget *parent = (*widget)->parent;
+
+         if (parent)
+           gtk_widget_translate_coordinates (*widget, parent, *x, *y, x, y);
+
+         *widget = parent;
+       }
+      else
+       break;
+    }
+  while (*widget);
+
+  return return_value;
+}
+
+static void
+gtk_tooltip_show_tooltip (GdkDisplay *display)
+{
+  gint x, y;
+  gint w, h;
+  gint monitor_num;
+  GdkScreen *screen;
+  GdkRectangle monitor;
+
+  GdkWindow *window;
+  GtkWidget *tooltip_widget;
+  GtkWidget *pointer_widget;
+  GtkTooltip *tooltip;
+  gboolean has_tooltip;
+  gboolean return_value = FALSE;
+
+  tooltip = g_object_get_data (G_OBJECT (display),
+                              "gdk-display-current-tooltip");
+
+  if (tooltip->keyboard_mode_enabled)
+    {
+      pointer_widget = tooltip_widget = tooltip->keyboard_widget;
+    }
+  else
+    {
+      window = tooltip->last_window;
+
+      gdk_window_get_origin (window, &x, &y);
+      x = tooltip->last_x - x;
+      y = tooltip->last_y - y;
+
+      if (!window)
+       return;
+
+      pointer_widget = tooltip_widget = find_widget_under_pointer (window,
+                                                                  &x, &y);
+    }
+
+  if (!tooltip_widget)
+    return;
+
+  g_object_get (tooltip_widget, "has-tooltip", &has_tooltip, NULL);
+
+  g_assert (tooltip != NULL);
+
+  return_value = gtk_tooltip_run_requery (&tooltip_widget, tooltip, &x, &y);
+  if (!return_value)
+    return;
+
+  if (!tooltip->current_window)
+    {
+      if (gtk_widget_get_tooltip_window (tooltip_widget))
+       tooltip->current_window = gtk_widget_get_tooltip_window (tooltip_widget);
+      else
+       tooltip->current_window = GTK_WINDOW (GTK_TOOLTIP (tooltip)->window);
+    }
+
+  /* Position the tooltip */
+  /* FIXME: should we swap this when RTL is enabled? */
+  if (tooltip->keyboard_mode_enabled)
+    {
+      gdk_window_get_origin (tooltip_widget->window, &x, &y);
+      if (GTK_WIDGET_NO_WINDOW (tooltip_widget))
+        {
+         x += tooltip_widget->allocation.x;
+         y += tooltip_widget->allocation.y;
+       }
+
+      /* For keyboard mode we position the tooltip below the widget,
+       * right of the center of the widget.
+       */
+      x += tooltip_widget->allocation.width / 2;
+      y += tooltip_widget->allocation.height + 4;
+    }
+  else
+    {
+      guint cursor_size;
+
+      x = tooltip->last_x;
+      y = tooltip->last_y;
+
+      /* For mouse mode, we position the tooltip right of the cursor,
+       * a little below the cursor's center.
+       */
+      cursor_size = gdk_display_get_default_cursor_size (display);
+      x += cursor_size / 2;
+      y += cursor_size / 2;
+    }
+
+  if (tooltip->current_window)
+    {
+      GtkRequisition requisition;
+
+      gtk_widget_size_request (GTK_WIDGET (tooltip->current_window), &requisition);
+      w = requisition.width;
+      h = requisition.height;
+    }
+
+  screen = gtk_widget_get_screen (tooltip_widget);
+
+  if (screen != gtk_widget_get_screen (tooltip->window))
+    {
+      g_signal_handlers_disconnect_by_func (display,
+                                           gtk_tooltip_display_closed,
+                                           tooltip);
+
+      gtk_window_set_screen (GTK_WINDOW (tooltip->window), screen);
+
+      g_signal_connect (display, "closed",
+                       G_CALLBACK (gtk_tooltip_display_closed), tooltip);
+    }
+
+  tooltip->tooltip_widget = tooltip_widget;
+
+  monitor_num = gdk_screen_get_monitor_at_point (screen, x, y);
+  gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
+
+  if (x + w > monitor.x + monitor.width)
+    x -= x - (monitor.x + monitor.width) + w;
+  else if (x < monitor.x)
+    x = monitor.x;
+
+  if (y + h > monitor.y + monitor.height)
+    y -= y - (monitor.y + monitor.height) + h;
+
+  /* Show it */
+  if (tooltip->current_window)
+    {
+      gtk_window_move (GTK_WINDOW (tooltip->current_window), x, y);
+      gtk_widget_show (GTK_WIDGET (tooltip->current_window));
+    }
+
+  /* Now a tooltip is visible again on the display, make sure browse
+   * mode is enabled.
+   */
+  tooltip->browse_mode_enabled = TRUE;
+  if (tooltip->browse_mode_timeout_id)
+    {
+      g_source_remove (tooltip->browse_mode_timeout_id);
+      tooltip->browse_mode_timeout_id = 0;
+    }
+}
+
+static void
+gtk_tooltip_hide_tooltip (GtkTooltip *tooltip)
+{
+  if (!tooltip || !GTK_TOOLTIP_VISIBLE (tooltip))
+    return;
+
+  tooltip->tooltip_widget = NULL;
+
+  if (tooltip->timeout_id)
+    {
+      g_source_remove (tooltip->timeout_id);
+      tooltip->timeout_id = 0;
+    }
+
+  if (!tooltip->keyboard_mode_enabled)
+    {
+      guint timeout;
+      GtkSettings *settings;
+
+      settings = gtk_widget_get_settings (GTK_WIDGET (tooltip->window));
+
+      g_object_get (settings,
+                   "gtk-tooltip-browse-mode-timeout", &timeout,
+                   NULL);
+
+      /* The tooltip is gone, after (by default, should be configurable) 500ms
+       * we want to turn off browse mode
+       */
+      if (!tooltip->browse_mode_timeout_id)
+       tooltip->browse_mode_timeout_id =
+         g_timeout_add_full (0, timeout,
+                             tooltip_browse_mode_expired,
+                             g_object_ref (tooltip),
+                             g_object_unref);
+    }
+  else
+    {
+      if (tooltip->browse_mode_timeout_id)
+        {
+         g_source_remove (tooltip->browse_mode_timeout_id);
+         tooltip->browse_mode_timeout_id = 0;
+       }
+    }
+
+  if (tooltip->current_window)
+    {
+      gtk_widget_hide (GTK_WIDGET (tooltip->current_window));
+      tooltip->current_window = NULL;
+    }
+}
+
+static gint
+tooltip_popup_timeout (gpointer data)
+{
+  GdkDisplay *display;
+  GtkTooltip *tooltip;
+
+  GDK_THREADS_ENTER ();
+
+  display = GDK_DISPLAY_OBJECT (data);
+
+  gtk_tooltip_show_tooltip (display);
+
+  tooltip = g_object_get_data (G_OBJECT (display),
+                              "gdk-display-current-tooltip");
+  tooltip->timeout_id = 0;
+
+  GDK_THREADS_LEAVE ();
+
+  return FALSE;
+}
+
+static void
+gtk_tooltip_start_delay (GdkDisplay *display)
+{
+  guint timeout;
+  GtkTooltip *tooltip;
+  GtkSettings *settings;
+
+  tooltip = g_object_get_data (G_OBJECT (display),
+                              "gdk-display-current-tooltip");
+
+  if (tooltip && GTK_TOOLTIP_VISIBLE (tooltip))
+    return;
+
+  if (tooltip->timeout_id)
+    g_source_remove (tooltip->timeout_id);
+
+  settings = gtk_widget_get_settings (GTK_WIDGET (tooltip->window));
+
+  if (tooltip->browse_mode_enabled)
+    g_object_get (settings, "gtk-tooltip-browse-timeout", &timeout, NULL);
+  else
+    g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL);
+
+  tooltip->timeout_id = g_timeout_add_full (0, timeout,
+                                           tooltip_popup_timeout,
+                                           g_object_ref (display),
+                                           g_object_unref);
+}
+
+void
+_gtk_tooltip_focus_in (GtkWidget *widget)
+{
+  gint x, y;
+  gboolean return_value = FALSE;
+  GdkDisplay *display;
+  GtkTooltip *tooltip;
+
+  /* Get current tooltip for this display */
+  display = gtk_widget_get_display (widget);
+  tooltip = g_object_get_data (G_OBJECT (display),
+                              "gdk-display-current-tooltip");
+
+  /* Check if keyboard mode is enabled at this moment */
+  if (!tooltip || !tooltip->keyboard_mode_enabled)
+    return;
+
+  if (tooltip->keyboard_widget)
+    g_object_unref (tooltip->keyboard_widget);
+
+  tooltip->keyboard_widget = g_object_ref (widget);
+
+  gdk_window_get_pointer (widget->window, &x, &y, NULL);
+
+  return_value = gtk_tooltip_run_requery (&widget, tooltip, &x, &y);
+  if (!return_value)
+    {
+      gtk_tooltip_hide_tooltip (tooltip);
+      return;
+    }
+
+  if (!tooltip->current_window)
+    {
+      if (gtk_widget_get_tooltip_window (widget))
+       tooltip->current_window = gtk_widget_get_tooltip_window (widget);
+      else
+       tooltip->current_window = GTK_WINDOW (GTK_TOOLTIP (tooltip)->window);
+    }
+
+  gtk_tooltip_show_tooltip (display);
+}
+
+void
+_gtk_tooltip_focus_out (GtkWidget *widget)
+{
+  GdkDisplay *display;
+  GtkTooltip *tooltip;
+
+  /* Get current tooltip for this display */
+  display = gtk_widget_get_display (widget);
+  tooltip = g_object_get_data (G_OBJECT (display),
+                              "gdk-display-current-tooltip");
+
+  if (!tooltip || !tooltip->keyboard_mode_enabled)
+    return;
+
+  if (tooltip->keyboard_widget)
+    {
+      g_object_unref (tooltip->keyboard_widget);
+      tooltip->keyboard_widget = NULL;
+    }
+
+  gtk_tooltip_hide_tooltip (tooltip);
+}
+
+void
+_gtk_tooltip_toggle_keyboard_mode (GtkWidget *widget)
+{
+  GdkDisplay *display;
+  GtkTooltip *tooltip;
+
+  display = gtk_widget_get_display (widget);
+  tooltip = g_object_get_data (G_OBJECT (display),
+                              "gdk-display-current-tooltip");
+
+  if (!tooltip)
+    {
+      tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL);
+      g_object_set_data_full (G_OBJECT (display),
+                             "gdk-display-current-tooltip",
+                             tooltip, g_object_unref);
+    }
+
+  tooltip->keyboard_mode_enabled ^= 1;
+
+  if (tooltip->keyboard_mode_enabled)
+    {
+      tooltip->keyboard_widget = g_object_ref (widget);
+      _gtk_tooltip_focus_in (widget);
+    }
+  else
+    {
+      if (tooltip->keyboard_widget)
+        {
+         g_object_unref (tooltip->keyboard_widget);
+         tooltip->keyboard_widget = NULL;
+       }
+
+      gtk_tooltip_hide_tooltip (tooltip);
+    }
+}
+
+void
+_gtk_tooltip_hide (GtkWidget *widget)
+{
+  GtkWidget *toplevel;
+  GdkDisplay *display;
+  GtkTooltip *tooltip;
+
+  display = gtk_widget_get_display (widget);
+  tooltip = g_object_get_data (G_OBJECT (display),
+                              "gdk-display-current-tooltip");
+
+  if (!tooltip || !GTK_TOOLTIP_VISIBLE (tooltip) || !tooltip->tooltip_widget)
+    return;
+
+  toplevel = gtk_widget_get_toplevel (widget);
+
+  if (widget == tooltip->tooltip_widget
+      || toplevel->window == tooltip->toplevel_window)
+    gtk_tooltip_hide_tooltip (tooltip);
+}
+
+void
+_gtk_tooltip_handle_event (GdkEvent *event)
+{
+  gint x, y;
+  gboolean return_value = FALSE;
+  GtkWidget *has_tooltip_widget = NULL;
+  GdkDisplay *display;
+  GtkTooltip *current_tooltip;
+
+  has_tooltip_widget = find_topmost_widget_coords_from_event (event, &x, &y);
+  display = gdk_drawable_get_display (event->any.window);
+  current_tooltip = g_object_get_data (G_OBJECT (display),
+                                      "gdk-display-current-tooltip");
+
+  if (current_tooltip)
+    {
+      current_tooltip->last_window = event->any.window;
+      gdk_event_get_root_coords (event,
+                               &current_tooltip->last_x,
+                               &current_tooltip->last_y);
+    }
+
+  if (current_tooltip && current_tooltip->keyboard_mode_enabled)
+    {
+      has_tooltip_widget = current_tooltip->keyboard_widget;
+      if (!has_tooltip_widget)
+       return;
+
+      return_value = gtk_tooltip_run_requery (&has_tooltip_widget,
+                                             current_tooltip,
+                                             &x, &y);
+
+      if (!return_value)
+       gtk_tooltip_hide_tooltip (current_tooltip);
+      else
+       gtk_tooltip_start_delay (display);
+
+      return;
+    }
+
+  /* Always poll for a next motion event */
+  if (event->type == GDK_MOTION_NOTIFY && event->motion.is_hint)
+    gdk_window_get_pointer (event->any.window, NULL, NULL, NULL);
+
+  /* Hide the tooltip when there's no new tooltip widget */
+  if (!has_tooltip_widget)
+    {
+      if (current_tooltip && GTK_TOOLTIP_VISIBLE (current_tooltip))
+       gtk_tooltip_hide_tooltip (current_tooltip);
+
+      return;
+    }
+
+  switch (event->type)
+    {
+      case GDK_BUTTON_PRESS:
+      case GDK_2BUTTON_PRESS:
+      case GDK_3BUTTON_PRESS:
+      case GDK_KEY_PRESS:
+      case GDK_DRAG_ENTER:
+      case GDK_GRAB_BROKEN:
+       gtk_tooltip_hide_tooltip (current_tooltip);
+       break;
+
+      case GDK_MOTION_NOTIFY:
+      case GDK_ENTER_NOTIFY:
+      case GDK_LEAVE_NOTIFY:
+      case GDK_SCROLL:
+       if (current_tooltip)
+         {
+           return_value = gtk_tooltip_run_requery (&has_tooltip_widget,
+                                                   current_tooltip,
+                                                   &x, &y);
+
+           if (!return_value)
+             gtk_tooltip_hide_tooltip (current_tooltip);
+           else
+             gtk_tooltip_start_delay (display);
+         }
+       else
+         {
+           /* Need a new tooltip for this display */
+           current_tooltip = g_object_new (GTK_TYPE_TOOLTIP, NULL);
+           g_object_set_data_full (G_OBJECT (display),
+                                   "gdk-display-current-tooltip",
+                                   current_tooltip, g_object_unref);
+
+           current_tooltip->last_window = event->any.window;
+           gdk_event_get_root_coords (event,
+                                      &current_tooltip->last_x,
+                                      &current_tooltip->last_y);
+
+           gtk_tooltip_start_delay (display);
+         }
+       break;
+
+      default:
+       break;
+    }
+}
diff --git a/gtk/gtktooltip.h b/gtk/gtktooltip.h
new file mode 100644 (file)
index 0000000..69b53e7
--- /dev/null
@@ -0,0 +1,55 @@
+/* gtktooltip.h
+ *
+ * Copyright (C) 2006-2007 Imendio AB
+ * Contact: Kristian Rietveld <kris@imendio.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GTK_TOOLTIP__
+#define __GTK_TOOLTIP__
+
+#include "gtkwidget.h"
+#include "gtkwindow.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_TOOLTIP                 (gtk_tooltip_get_type ())
+
+GType gtk_tooltip_get_type (void);
+
+void gtk_tooltip_set_markup            (GtkTooltip  *tooltip,
+                                       const gchar *markup);
+void gtk_tooltip_set_icon              (GtkTooltip  *tooltip,
+                                       GdkPixbuf   *pixbuf);
+void gtk_tooltip_set_icon_from_stock   (GtkTooltip  *tooltip,
+                                       const gchar *stock_id,
+                                       GtkIconSize  size);
+void gtk_tooltip_set_custom           (GtkTooltip  *tooltip,
+                                       GtkWidget   *custom_widget);
+
+void gtk_tooltip_trigger_tooltip_query (GdkDisplay  *display);
+
+
+void _gtk_tooltip_focus_in             (GtkWidget   *widget);
+void _gtk_tooltip_focus_out            (GtkWidget   *widget);
+void _gtk_tooltip_toggle_keyboard_mode (GtkWidget   *widget);
+void _gtk_tooltip_handle_event         (GdkEvent    *event);
+void _gtk_tooltip_hide                 (GtkWidget   *widget);
+
+G_END_DECLS
+
+#endif /* __GTK_TOOLTIP__ */
index b92c2345734c1ffcd0503e6368d423ba32f80487..93553afd48224dc068300686128c9142b310d3d7 100644 (file)
@@ -50,6 +50,7 @@
 #include "gdk/gdkkeysyms.h"
 #include "gtkaccessible.h"
 #include "gtktooltips.h"
+#include "gtktooltip.h"
 #include "gtkinvisible.h"
 #include "gtkalias.h"
 
@@ -121,6 +122,7 @@ enum {
   CAN_ACTIVATE_ACCEL,
   GRAB_BROKEN,
   COMPOSITED_CHANGED,
+  QUERY_TOOLTIP,
   KEYNAV_FAILED,
   DRAG_FAILED,
   LAST_SIGNAL
@@ -145,7 +147,9 @@ enum {
   PROP_STYLE,
   PROP_EVENTS,
   PROP_EXTENSION_EVENTS,
-  PROP_NO_SHOW_ALL
+  PROP_NO_SHOW_ALL,
+  PROP_HAS_TOOLTIP,
+  PROP_TOOLTIP_MARKUP
 };
 
 typedef        struct  _GtkStateData    GtkStateData;
@@ -158,7 +162,6 @@ struct _GtkStateData
   guint                use_forall : 1;
 };
 
-
 /* --- prototypes --- */
 static void    gtk_widget_class_init           (GtkWidgetClass     *klass);
 static void    gtk_widget_base_class_finalize  (GtkWidgetClass     *klass);
@@ -190,6 +193,11 @@ static void        gtk_widget_direction_changed     (GtkWidget         *widget,
                                                  GtkTextDirection   previous_direction);
 
 static void    gtk_widget_real_grab_focus       (GtkWidget         *focus_widget);
+static gboolean gtk_widget_real_query_tooltip    (GtkWidget         *widget,
+                                                 gint               x,
+                                                 gint               y,
+                                                 gboolean           keyboard_tip,
+                                                 GtkTooltip        *tooltip);
 static gboolean gtk_widget_real_show_help        (GtkWidget         *widget,
                                                   GtkWidgetHelpType  help_type);
 
@@ -230,6 +238,10 @@ static GdkScreen *      gtk_widget_get_screen_unchecked         (GtkWidget
 static void            gtk_widget_queue_shallow_draw           (GtkWidget        *widget);
 static gboolean         gtk_widget_real_can_activate_accel      (GtkWidget *widget,
                                                                  guint      signal_id);
+
+static void             gtk_widget_set_has_tooltip              (GtkWidget *widget,
+                                                                gboolean   has_tooltip,
+                                                                gboolean   force);
      
 static void gtk_widget_set_usize_internal (GtkWidget *widget,
                                           gint       width,
@@ -261,6 +273,9 @@ static GQuark               quark_pango_context = 0;
 static GQuark          quark_rc_style = 0;
 static GQuark          quark_accessible_object = 0;
 static GQuark          quark_mnemonic_labels = 0;
+static GQuark          quark_tooltip_markup = 0;
+static GQuark          quark_has_tooltip = 0;
+static GQuark          quark_tooltip_window = 0;
 GParamSpecPool         *_gtk_widget_child_property_pool = NULL;
 GObjectNotifyContext   *_gtk_widget_child_property_notify_context = NULL;
 
@@ -336,6 +351,9 @@ gtk_widget_class_init (GtkWidgetClass *klass)
   quark_rc_style = g_quark_from_static_string ("gtk-rc-style");
   quark_accessible_object = g_quark_from_static_string ("gtk-accessible-object");
   quark_mnemonic_labels = g_quark_from_static_string ("gtk-mnemonic-labels");
+  quark_tooltip_markup = g_quark_from_static_string ("gtk-tooltip-markup");
+  quark_has_tooltip = g_quark_from_static_string ("gtk-has-tooltip");
+  quark_tooltip_window = g_quark_from_static_string ("gtk-tooltip-window");
 
   style_property_spec_pool = g_param_spec_pool_new (FALSE);
   _gtk_widget_child_property_pool = g_param_spec_pool_new (TRUE);
@@ -407,6 +425,7 @@ gtk_widget_class_init (GtkWidgetClass *klass)
   klass->screen_changed = NULL;
   klass->can_activate_accel = gtk_widget_real_can_activate_accel;
   klass->grab_broken_event = NULL;
+  klass->query_tooltip = gtk_widget_real_query_tooltip;
 
   klass->show_help = gtk_widget_real_show_help;
   
@@ -548,6 +567,47 @@ gtk_widget_class_init (GtkWidgetClass *klass)
                                                         P_("Whether gtk_widget_show_all() should not affect this widget"),
                                                         FALSE,
                                                         GTK_PARAM_READWRITE));
+
+/**
+ * GtkWidget:has-tooltip:
+ *
+ * Enables or disables the emission of GtkWidget::query-tooltip on @widget.  A
+ * value of %TRUE indicates that @widget can have a tooltip, in this case
+ * the widget will be queried using GtkWidget::query-tooltip to determine
+ * whether it will provide a tooltip or not.
+ *
+ * Since: 2.12
+ */
+  g_object_class_install_property (gobject_class,
+                                  PROP_HAS_TOOLTIP,
+                                  g_param_spec_boolean ("has-tooltip",
+                                                        P_("Has tooltip"),
+                                                        P_("Whether this widget has a tooltip"),
+                                                        FALSE,
+                                                        GTK_PARAM_READWRITE));
+
+/**
+ * GtkWidget:tooltip-markup:
+ *
+ * Sets the text of tooltip to be the given string, which is marked up
+ * with the <link linkend="PangoMarkupFormat">Pango text markup language</link>.
+ * Also see gtk_tooltip_set_markup().
+ *
+ * This is a convenience property which will take care of getting the
+ * tooltip shown if the given string is not %NULL: GtkWidget:has-tooltip
+ * will automatically be set to %TRUE and there will be taken care of
+ * GtkWidget::query-tooltip in the default signal handler.
+ *
+ * Since: 2.12
+ */
+  g_object_class_install_property (gobject_class,
+                                  PROP_TOOLTIP_MARKUP,
+                                  g_param_spec_string ("tooltip-markup",
+                                                       P_("Tooltip markup"),
+                                                       P_("The contents of the tooltip for this widget"),
+                                                       NULL,
+                                                       GTK_PARAM_READWRITE));
+
   widget_signals[SHOW] =
     g_signal_new (I_("show"),
                  G_TYPE_FROM_CLASS (gobject_class),
@@ -1450,6 +1510,45 @@ gtk_widget_class_init (GtkWidgetClass *klass)
                  _gtk_marshal_BOOLEAN__BOXED,
                  G_TYPE_BOOLEAN, 1,
                  GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+  /**
+   * GtkWidget::query-tooltip:
+   * @widget: the object which received the signal
+   * @x: the x coordinate of the cursor position where the request has been
+   *     emitted, relative to the widget's allocation
+   * @y: the y coordinate of the cursor position where the request has been
+   *     emitted, relative to the widget's allocation
+   * @keyboard_mode: %TRUE if the tooltip was trigged using the keyboard
+   * @tooltip: a #GtkTooltip
+   *
+   * Emitted when the gtk-tooltip-timeout has expired with the cursor
+   * hovering "above" @widget; or emitted when @widget got focus in
+   * keyboard mode.
+   *
+   * Using the given coordinates, the signal handler should determine
+   * whether a tooltip should be shown for @widget.  If this is the case
+   * %TRUE should be returned, %FALSE otherwise.  Note that if
+   * @keyboard_mode is %TRUE, the values of @x and @y are undefined and
+   * should not be used.
+   *
+   * The signal handler is free to manipulate @tooltip with the therefore
+   * destined function calls.
+   *
+   * Returns: %TRUE if @tooltip should be shown right now, %FALSE otherwise.
+   *
+   * Since: 2.12
+   */
+  widget_signals[QUERY_TOOLTIP] =
+    g_signal_new (I_("query-tooltip"),
+                 G_TYPE_FROM_CLASS (gobject_class),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (GtkWidgetClass, query_tooltip),
+                 _gtk_boolean_handled_accumulator, NULL,
+                 _gtk_marshal_BOOLEAN__INT_INT_BOOLEAN_OBJECT,
+                 G_TYPE_BOOLEAN, 4,
+                 G_TYPE_INT,
+                 G_TYPE_INT,
+                 G_TYPE_BOOLEAN,
+                 GTK_TYPE_TOOLTIP);
 /**
  * GtkWidget::popup-menu
  * @widget: the object which received the signal
@@ -1730,7 +1829,10 @@ gtk_widget_set_property (GObject         *object,
 
   switch (prop_id)
     {
+      gboolean tmp;
       guint32 saved_flags;
+      gchar *tooltip_markup;
+      GtkWindow *tooltip_window;
       
     case PROP_NAME:
       gtk_widget_set_name (widget, g_value_get_string (value));
@@ -1805,6 +1907,21 @@ gtk_widget_set_property (GObject         *object,
     case PROP_NO_SHOW_ALL:
       gtk_widget_set_no_show_all (widget, g_value_get_boolean (value));
       break;
+    case PROP_HAS_TOOLTIP:
+      gtk_widget_set_has_tooltip (widget, g_value_get_boolean (value), FALSE);
+      break;
+    case PROP_TOOLTIP_MARKUP:
+      tooltip_markup = g_object_get_qdata (object, quark_tooltip_markup);
+      tooltip_window = g_object_get_qdata (object, quark_tooltip_window);
+
+      tooltip_markup = g_value_dup_string (value);
+
+      g_object_set_qdata_full (object, quark_tooltip_markup,
+                              tooltip_markup, g_free);
+
+      tmp = (tooltip_window != NULL || tooltip_markup != NULL);
+      gtk_widget_set_has_tooltip (widget, tmp, FALSE);
+      break;
     default:
       break;
     }
@@ -1899,6 +2016,12 @@ gtk_widget_get_property (GObject         *object,
     case PROP_NO_SHOW_ALL:
       g_value_set_boolean (value, gtk_widget_get_no_show_all (widget));
       break;
+    case PROP_HAS_TOOLTIP:
+      g_value_set_boolean (value, GPOINTER_TO_UINT (g_object_get_qdata (object, quark_has_tooltip)));
+      break;
+    case PROP_TOOLTIP_MARKUP:
+      g_value_set_string (value, g_object_get_qdata (object, quark_tooltip_markup));
+      break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -2490,6 +2613,7 @@ gtk_widget_unmap (GtkWidget *widget)
     {
       if (GTK_WIDGET_NO_WINDOW (widget))
        gdk_window_invalidate_rect (widget->window, &widget->allocation, FALSE);
+      _gtk_tooltip_hide (widget);
       g_signal_emit (widget, widget_signals[UNMAP], 0);
     }
 }
@@ -2548,7 +2672,9 @@ gtk_widget_realize (GtkWidget *widget)
       gtk_widget_ensure_style (widget);
       
       g_signal_emit (widget, widget_signals[REALIZE], 0);
-      
+
+      gtk_widget_set_has_tooltip (widget, GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip)), TRUE);
+
       if (GTK_WIDGET_HAS_SHAPE_MASK (widget))
        {
          shape_info = g_object_get_qdata (G_OBJECT (widget), quark_shape_info);
@@ -2601,6 +2727,7 @@ gtk_widget_unrealize (GtkWidget *widget)
   if (GTK_WIDGET_REALIZED (widget))
     {
       g_object_ref (widget);
+      _gtk_tooltip_hide (widget);
       g_signal_emit (widget, widget_signals[UNREALIZE], 0);
       GTK_WIDGET_UNSET_FLAGS (widget, GTK_REALIZED | GTK_MAPPED);
       g_object_unref (widget);
@@ -3897,6 +4024,7 @@ gtk_widget_event_internal (GtkWidget *widget,
          break;
        case GDK_DESTROY:
          signal_num = DESTROY_EVENT;
+         _gtk_tooltip_hide (widget);
          break;
        case GDK_KEY_PRESS:
          signal_num = KEY_PRESS_EVENT;
@@ -3912,6 +4040,10 @@ gtk_widget_event_internal (GtkWidget *widget,
          break;
        case GDK_FOCUS_CHANGE:
          signal_num = event->focus_change.in ? FOCUS_IN_EVENT : FOCUS_OUT_EVENT;
+         if (event->focus_change.in)
+           _gtk_tooltip_focus_in (widget);
+         else
+           _gtk_tooltip_focus_out (widget);
          break;
        case GDK_CONFIGURE:
          signal_num = CONFIGURE_EVENT;
@@ -4354,6 +4486,28 @@ gtk_widget_real_grab_focus (GtkWidget *focus_widget)
     }
 }
 
+static gboolean
+gtk_widget_real_query_tooltip (GtkWidget  *widget,
+                              gint        x,
+                              gint        y,
+                              gboolean    keyboard_tip,
+                              GtkTooltip *tooltip)
+{
+  gchar *tooltip_markup;
+  gboolean has_tooltip;
+
+  tooltip_markup = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_markup);
+  has_tooltip = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (widget), quark_has_tooltip));
+
+  if (has_tooltip && tooltip_markup)
+    {
+      gtk_tooltip_set_markup (tooltip, tooltip_markup);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
 static gboolean
 gtk_widget_real_show_help (GtkWidget        *widget,
                            GtkWidgetHelpType help_type)
@@ -4361,6 +4515,8 @@ gtk_widget_real_show_help (GtkWidget        *widget,
   if (help_type == GTK_WIDGET_HELP_TOOLTIP)
     {
       _gtk_tooltips_toggle_keyboard_mode (widget);
+      _gtk_tooltip_toggle_keyboard_mode (widget);
+
       return TRUE;
     }
   else
@@ -5256,6 +5412,7 @@ do_screen_change (GtkWidget *widget,
            g_object_set_qdata (G_OBJECT (widget), quark_pango_context, NULL);
        }
       
+      _gtk_tooltip_hide (widget);
       g_signal_emit (widget, widget_signals[SCREEN_CHANGED], 0, old_screen);
     }
 }
@@ -8219,5 +8376,122 @@ gtk_widget_set_no_show_all (GtkWidget *widget,
   g_object_notify (G_OBJECT (widget), "no-show-all");
 }
 
+
+static void
+gtk_widget_set_has_tooltip (GtkWidget *widget,
+                           gboolean   has_tooltip,
+                           gboolean   force)
+{
+  gboolean priv_has_tooltip;
+
+  priv_has_tooltip = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (widget),
+                                      quark_has_tooltip));
+
+  if (priv_has_tooltip != has_tooltip || force)
+    {
+      priv_has_tooltip = has_tooltip;
+
+      if (priv_has_tooltip)
+        {
+         if (GTK_WIDGET_REALIZED (widget) && GTK_WIDGET_NO_WINDOW (widget))
+           gdk_window_set_events (widget->window,
+                                  gdk_window_get_events (widget->window) |
+                                  GDK_LEAVE_NOTIFY_MASK |
+                                  GDK_POINTER_MOTION_MASK |
+                                  GDK_POINTER_MOTION_HINT_MASK);
+
+         if (!GTK_WIDGET_NO_WINDOW (widget))
+             gtk_widget_add_events (widget,
+                                    GDK_LEAVE_NOTIFY_MASK |
+                                    GDK_POINTER_MOTION_MASK |
+                                    GDK_POINTER_MOTION_HINT_MASK);
+       }
+
+      g_object_set_qdata (G_OBJECT (widget), quark_has_tooltip,
+                         GUINT_TO_POINTER (priv_has_tooltip));
+    }
+}
+
+/**
+ * gtk_widget_set_tooltip_window:
+ * @widget: a #GtkWidget
+ * @custom_window: a #GtkWindow, or %NULL
+ *
+ * Replaces the default, usually yellow, window used for displaying
+ * tooltips with @custom_window.  GTK+ will take care of showing and
+ * hiding @custom_window at the right moment, to behave likewise as
+ * the default tooltip window.  If @custom_window is %NULL, the default
+ * tooltip window will be used.
+ *
+ * Since: 2.12
+ */
+void
+gtk_widget_set_tooltip_window (GtkWidget *widget,
+                              GtkWindow *custom_window)
+{
+  gboolean tmp;
+  gchar *tooltip_markup;
+  GtkWindow *tooltip_window;
+
+  g_return_if_fail (GTK_IS_WIDGET (widget));
+  if (custom_window)
+    g_return_if_fail (GTK_IS_WINDOW (custom_window));
+
+  tooltip_window = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_window);
+  tooltip_markup = g_object_get_qdata (G_OBJECT (widget), quark_tooltip_markup);
+
+  if (custom_window)
+    g_object_ref (custom_window);
+
+  if (tooltip_window)
+    g_object_unref (tooltip_window);
+
+  tooltip_window = custom_window;
+  g_object_set_qdata_full (G_OBJECT (widget), quark_tooltip_window,
+                          tooltip_window, g_object_unref);
+
+  tmp = (tooltip_window != NULL || tooltip_markup != NULL);
+  gtk_widget_set_has_tooltip (widget, tmp, FALSE);
+
+  if (tmp)
+    gtk_widget_trigger_tooltip_query (widget);
+}
+
+/**
+ * gtk_widget_get_tooltip_window:
+ * @widget: a #GtkWidget
+ *
+ * Returns the #GtkWindow of the current tooltip.  This can be the
+ * GtkWindow created by default, or the custom tooltip window set
+ * using gtk_widget_set_tooltip_window().
+ *
+ * Return value: The #GtkWindow of the current tooltip.
+ *
+ * Since: 2.12
+ */
+GtkWindow *
+gtk_widget_get_tooltip_window (GtkWidget *widget)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+
+  return g_object_get_qdata (G_OBJECT (widget), quark_tooltip_window);
+}
+
+/**
+ * gtk_widget_trigger_tooltip_query:
+ * @widget: a #GtkWidget
+ *
+ * Triggers a tooltip query on the display where the toplevel of @widget
+ * is located.  See gtk_tooltip_trigger_tooltip_query() for more
+ * information.
+ *
+ * Since: 2.12
+ */
+void
+gtk_widget_trigger_tooltip_query (GtkWidget *widget)
+{
+  gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (widget));
+}
+
 #define __GTK_WIDGET_C__
 #include "gtkaliasdef.c"
index cb5841fbd3a97e2410568cbfc5918322ed639ecb..50374cc85aa42ac571f9c36fbd2034744e719961 100644 (file)
@@ -140,6 +140,8 @@ typedef struct _GtkWidgetClass         GtkWidgetClass;
 typedef struct _GtkWidgetAuxInfo   GtkWidgetAuxInfo;
 typedef struct _GtkWidgetShapeInfo GtkWidgetShapeInfo;
 typedef struct _GtkClipboard      GtkClipboard;
+typedef struct _GtkTooltip         GtkTooltip;
+typedef struct _GtkWindow          GtkWindow;
 typedef void     (*GtkCallback)        (GtkWidget        *widget,
                                        gpointer          data);
 
@@ -409,8 +411,13 @@ struct _GtkWidgetClass
 
   void         (* composited_changed) (GtkWidget *widget);
 
+  gboolean     (* query_tooltip)      (GtkWidget  *widget,
+                                      gint        x,
+                                      gint        y,
+                                      gboolean    keyboard_tooltip,
+                                      GtkTooltip *tooltip);
+
   /* Padding for future expansion */
-  void (*_gtk_reserved4) (void);
   void (*_gtk_reserved5) (void);
   void (*_gtk_reserved6) (void);
   void (*_gtk_reserved7) (void);
@@ -777,6 +784,12 @@ void   gtk_widget_add_mnemonic_label    (GtkWidget *widget,
 void   gtk_widget_remove_mnemonic_label (GtkWidget *widget,
                                         GtkWidget *label);
 
+void            gtk_widget_set_tooltip_window    (GtkWidget *widget,
+                                                 GtkWindow *custom_window);
+GtkWindow      *gtk_widget_get_tooltip_window    (GtkWidget *widget);
+void            gtk_widget_trigger_tooltip_query (GtkWidget *widget);
+
+
 GType           gtk_requisition_get_type (void) G_GNUC_CONST;
 GtkRequisition *gtk_requisition_copy     (const GtkRequisition *requisition);
 void            gtk_requisition_free     (GtkRequisition       *requisition);
index f022bb9eb13119f365c30a1002ce35144b83292f..4d4fc53d7e4b277c65b7b434af0a9a0a53e900e8 100644 (file)
@@ -44,7 +44,6 @@ G_BEGIN_DECLS
 #define GTK_WINDOW_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_WINDOW, GtkWindowClass))
 
 
-typedef struct _GtkWindow             GtkWindow;
 typedef struct _GtkWindowClass        GtkWindowClass;
 typedef struct _GtkWindowGeometryInfo GtkWindowGeometryInfo;
 typedef struct _GtkWindowGroup        GtkWindowGroup;
index 6972165a0356180a03adbd151fd6f23f00560465..3058726ad2767752e474782bf3c8420a1dcf56f5 100644 (file)
@@ -82,7 +82,8 @@ noinst_PROGRAMS =                     \
        pixbuf-threads                  \
        testmerge                       \
        testactions                     \
-       testgrouping
+       testgrouping                    \
+       testtooltips
 
 autotestfilechooser_DEPENDENCIES = $(TEST_DEPS)
 simple_DEPENDENCIES = $(TEST_DEPS)
@@ -134,6 +135,7 @@ testxinerama_DEPENDENCIES = $(TEST_DEPS)
 testmerge_DEPENDENCIES = $(TEST_DEPS)
 testactions_DEPENDENCIES = $(TEST_DEPS)
 testgrouping_DEPENDENCIES = $(TEST_DEPS)
+testtooltips_DEPENDENCIES = $(TEST_DEPS)
 
 autotestfilechooser_LDADD = $(LDADDS)
 simple_LDADD = $(LDADDS)
@@ -192,6 +194,7 @@ pixbuf_threads_LDADD = $(LDADDS) $(GLIB_LIBS)
 testmerge_LDADD = $(LDADDS)
 testactions_LDADD = $(LDADDS)
 testgrouping_LDADD = $(LDADDS)
+testtooltips_LDADD = $(LDADDS)
 
 autotestfilechooser_SOURCES =  \
        autotestfilechooser.c
@@ -267,6 +270,9 @@ testrecentchooser_SOURCES =         \
 testgrouping_SOURCES =         \
        testgrouping.c
 
+testtoooltips_SOURCES =                \
+       testtooltips.c
+
 EXTRA_DIST =                   \
        prop-editor.h           \
        testgtk.1               \
diff --git a/tests/testtooltips.c b/tests/testtooltips.c
new file mode 100644 (file)
index 0000000..8def511
--- /dev/null
@@ -0,0 +1,377 @@
+/* testtooltips.c: Test application for GTK+ >= 2.12 tooltips code
+ *
+ * Copyright (C) 2006-2007  Imendio AB
+ * Contact: Kristian Rietveld <kris@imendio.com>
+ *
+ * This work is provided "as is"; redistribution and modification
+ * in whole or in part, in any medium, physical or electronic is
+ * permitted without restriction.
+ *
+ * This work is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * In no event shall the authors or contributors be liable for any
+ * direct, indirect, incidental, special, exemplary, or consequential
+ * damages (including, but not limited to, procurement of substitute
+ * goods or services; loss of use, data, or profits; or business
+ * interruption) however caused and on any theory of liability, whether
+ * in contract, strict liability, or tort (including negligence or
+ * otherwise) arising in any way out of the use of this software, even
+ * if advised of the possibility of such damage.
+ */
+
+#include <gtk/gtk.h>
+
+static gboolean
+query_tooltip_cb (GtkWidget  *widget,
+                 gint        x,
+                 gint        y,
+                 gboolean    keyboard_tip,
+                 GtkTooltip *tooltip,
+                 gpointer    data)
+{
+  gtk_tooltip_set_markup (tooltip, gtk_button_get_label (GTK_BUTTON (widget)));
+  gtk_tooltip_set_icon_from_stock (tooltip, GTK_STOCK_DELETE,
+                                  GTK_ICON_SIZE_MENU);
+
+  return TRUE;
+}
+
+static gboolean
+query_tooltip_custom_cb (GtkWidget  *widget,
+                        gint        x,
+                        gint        y,
+                        gboolean    keyboard_tip,
+                        GtkTooltip *tooltip,
+                        gpointer    data)
+{
+  GdkColor color = { 0, 0, 65535 };
+  GtkWindow *window = gtk_widget_get_tooltip_window (widget);
+
+  gtk_widget_modify_bg (GTK_WIDGET (window), GTK_STATE_NORMAL, &color);
+
+  return TRUE;
+}
+
+static gboolean
+query_tooltip_text_view_cb (GtkWidget  *widget,
+                           gint        x,
+                           gint        y,
+                           gboolean    keyboard_tip,
+                           GtkTooltip *tooltip,
+                           gpointer    data)
+{
+  gint bx, by, trailing;
+  GtkTextTag *tag = data;
+  GtkTextIter iter;
+  GtkTextView *text_view = GTK_TEXT_VIEW (widget);
+
+  gtk_text_view_window_to_buffer_coords (text_view, GTK_TEXT_WINDOW_TEXT,
+                                        x, y, &bx, &by);
+  gtk_text_view_get_iter_at_position (text_view, &iter, &trailing, bx, by);
+
+  if (gtk_text_iter_has_tag (&iter, tag))
+    gtk_tooltip_set_markup (tooltip, "Tooltip on text tag");
+  else
+   return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+query_tooltip_tree_view_cb (GtkWidget  *widget,
+                           gint        x,
+                           gint        y,
+                           gboolean    keyboard_tip,
+                           GtkTooltip *tooltip,
+                           gpointer    data)
+{
+  GtkTreeIter iter;
+  GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+  GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
+  GtkTreePath *path = NULL;
+  gchar *tmp;
+  gchar *pathstring;
+
+  char buffer[512];
+
+  if (keyboard_tip)
+    {
+      /* Keyboard mode */
+      gtk_tree_view_get_cursor (tree_view, &path, NULL);
+
+      if (!path)
+       return FALSE;
+    }
+  else
+    {
+      /* Mouse mode */
+      if (!gtk_tree_view_get_path_at_pos (tree_view, x, y, &path, NULL, NULL, NULL))
+        return FALSE;
+    }
+
+  gtk_tree_model_get_iter (model, &iter, path);
+  gtk_tree_model_get (model, &iter, 0, &tmp, -1);
+  pathstring = gtk_tree_path_to_string (path);
+
+  snprintf (buffer, 511, "<b>Path %s:</b> %s", pathstring, tmp);
+  gtk_tooltip_set_markup (tooltip, buffer);
+
+  gtk_tree_path_free (path);
+  g_free (pathstring);
+  g_free (tmp);
+
+  return TRUE;
+}
+
+static GtkTreeModel *
+create_model (void)
+{
+  GtkTreeStore *store;
+  GtkTreeIter iter;
+
+  store = gtk_tree_store_new (1, G_TYPE_STRING);
+
+  /* A tree store with some random words ... */
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+                                    0, "File Manager", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+                                    0, "Gossip", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+                                    0, "System Settings", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+                                    0, "The GIMP", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+                                    0, "Terminal", -1);
+  gtk_tree_store_insert_with_values (store, &iter, NULL, 0,
+                                    0, "Word Processor", -1);
+
+  return GTK_TREE_MODEL (store);
+}
+
+static void
+selection_changed_cb (GtkTreeSelection *selection,
+                     GtkWidget        *tree_view)
+{
+  gtk_widget_trigger_tooltip_query (tree_view);
+}
+
+static struct Rectangle
+{
+  gint x;
+  gint y;
+  gfloat r;
+  gfloat g;
+  gfloat b;
+  const char *tooltip;
+}
+rectangles[] =
+{
+  { 10, 10, 0.0, 0.0, 0.9, "Blue box!" },
+  { 200, 170, 1.0, 0.0, 0.0, "Red thing" },
+  { 100, 50, 0.8, 0.8, 0.0, "Yellow thing" }
+};
+
+static gboolean
+query_tooltip_drawing_area_cb (GtkWidget  *widget,
+                              gint        x,
+                              gint        y,
+                              gboolean    keyboard_tip,
+                              GtkTooltip *tooltip,
+                              gpointer    data)
+{
+  gint i;
+
+  if (keyboard_tip)
+    return FALSE;
+
+  for (i = 0; i < G_N_ELEMENTS (rectangles); i++)
+    {
+      struct Rectangle *r = &rectangles[i];
+
+      if (r->x < x && x < r->x + 50
+         && r->y < y && y < r->y + 50)
+        {
+         gtk_tooltip_set_markup (tooltip, r->tooltip);
+         return TRUE;
+       }
+    }
+
+  return FALSE;
+}
+
+static gboolean
+drawing_area_expose (GtkWidget      *drawing_area,
+                    GdkEventExpose *event,
+                    gpointer        data)
+{
+  gint i;
+  cairo_t *cr;
+
+  gdk_window_get_pointer (drawing_area->window, NULL, NULL, NULL);
+
+  cr = gdk_cairo_create (drawing_area->window);
+
+  cairo_rectangle (cr, 0, 0,
+                  drawing_area->allocation.width,
+                  drawing_area->allocation.height);
+  cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+  cairo_fill (cr);
+
+  for (i = 0; i < G_N_ELEMENTS (rectangles); i++)
+    {
+      struct Rectangle *r = &rectangles[i];
+
+      cairo_rectangle (cr, r->x, r->y, 50, 50);
+      cairo_set_source_rgb (cr, r->r, r->g, r->b);
+      cairo_stroke (cr);
+
+      cairo_rectangle (cr, r->x, r->y, 50, 50);
+      cairo_set_source_rgba (cr, r->r, r->g, r->b, 0.5);
+      cairo_fill (cr);
+    }
+
+  cairo_destroy (cr);
+
+  return FALSE;
+}
+
+int
+main (int argc, char *argv[])
+{
+  GtkWidget *window;
+  GtkWidget *box;
+  GtkWidget *drawing_area;
+  GtkWidget *button;
+
+  GtkWidget *tooltip_window;
+  GtkWidget *tooltip_button;
+
+  GtkWidget *tree_view;
+  GtkTreeViewColumn *column;
+
+  GtkWidget *text_view;
+  GtkTextBuffer *buffer;
+  GtkTextIter iter;
+  GtkTextTag *tag;
+
+  gtk_init (&argc, &argv);
+
+  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_title (GTK_WINDOW (window), "Tooltips test");
+  gtk_container_set_border_width (GTK_CONTAINER (window), 10);
+  g_signal_connect (window, "delete_event",
+                   G_CALLBACK (gtk_main_quit), NULL);
+
+  box = gtk_vbox_new (FALSE, 3);
+  gtk_container_add (GTK_CONTAINER (window), box);
+
+  /* A check button using the tooltip-markup property */
+  button = gtk_check_button_new_with_label ("This one uses the tooltip-markup property");
+  g_object_set (button, "tooltip-markup", "Hello, I am a static tooltip.", NULL);
+  gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+  /* A check button using the query-tooltip signal */
+  button = gtk_check_button_new_with_label ("I use the query-tooltip signal");
+  g_object_set (button, "has-tooltip", TRUE, NULL);
+  g_signal_connect (button, "query-tooltip",
+                   G_CALLBACK (query_tooltip_cb), NULL);
+  gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+  /* A label */
+  button = gtk_label_new ("I am just a label");
+  gtk_label_set_selectable (GTK_LABEL (button), FALSE);
+  g_object_set (button, "tooltip-markup", "Label tooltip", NULL);
+  gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+  /* A selectable label */
+  button = gtk_label_new ("I am a selectable label");
+  gtk_label_set_selectable (GTK_LABEL (button), TRUE);
+  g_object_set (button, "tooltip-markup", "Another Label tooltip", NULL);
+  gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+  /* Another one, with a custom tooltip window */
+  button = gtk_check_button_new_with_label ("This one has a custom tooltip window!");
+  gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+  tooltip_window = gtk_window_new (GTK_WINDOW_POPUP);
+  tooltip_button = gtk_label_new ("blaat!");
+  gtk_container_add (GTK_CONTAINER (tooltip_window), tooltip_button);
+  gtk_widget_show (tooltip_button);
+
+  gtk_widget_set_tooltip_window (button, GTK_WINDOW (tooltip_window));
+  g_signal_connect (button, "query-tooltip",
+                   G_CALLBACK (query_tooltip_custom_cb), NULL);
+  g_object_set (button, "has-tooltip", TRUE, NULL);
+
+  /* An insensitive button */
+  button = gtk_button_new_with_label ("This one is insensitive");
+  gtk_widget_set_sensitive (button, FALSE);
+  g_object_set (button, "tooltip-markup", "Insensitive!", NULL);
+  gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+
+  /* Testcases from Kris without a tree view don't exist. */
+  tree_view = gtk_tree_view_new_with_model (create_model ());
+  gtk_widget_set_size_request (tree_view, 200, 240);
+
+  gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree_view),
+                                              0, "Test",
+                                              gtk_cell_renderer_text_new (),
+                                              "text", 0,
+                                              NULL);
+
+  g_object_set (tree_view, "has-tooltip", TRUE, NULL);
+  g_signal_connect (tree_view, "query-tooltip",
+                   G_CALLBACK (query_tooltip_tree_view_cb), NULL);
+  g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)),
+                   "changed", G_CALLBACK (selection_changed_cb), tree_view);
+
+  /* Set a tooltip on the column */
+  column = gtk_tree_view_get_column (GTK_TREE_VIEW (tree_view), 0);
+  gtk_tree_view_column_set_clickable (column, TRUE);
+  g_object_set (column->button, "tooltip-markup", "Header", NULL);
+
+  gtk_box_pack_start (GTK_BOX (box), tree_view, FALSE, FALSE, 2);
+
+  /* And a text view for Matthias */
+  buffer = gtk_text_buffer_new (NULL);
+
+  gtk_text_buffer_get_end_iter (buffer, &iter);
+  gtk_text_buffer_insert (buffer, &iter, "Hello, the text ", -1);
+
+  tag = gtk_text_buffer_create_tag (buffer, "bold", NULL);
+  g_object_set (tag, "weight", PANGO_WEIGHT_BOLD, NULL);
+
+  gtk_text_buffer_get_end_iter (buffer, &iter);
+  gtk_text_buffer_insert_with_tags (buffer, &iter, "in bold", -1, tag, NULL);
+
+  gtk_text_buffer_get_end_iter (buffer, &iter);
+  gtk_text_buffer_insert (buffer, &iter, " has a tooltip!", -1);
+
+  text_view = gtk_text_view_new_with_buffer (buffer);
+  gtk_widget_set_size_request (text_view, 200, 50);
+
+  g_object_set (text_view, "has-tooltip", TRUE, NULL);
+  g_signal_connect (text_view, "query-tooltip",
+                   G_CALLBACK (query_tooltip_text_view_cb), tag);
+
+  gtk_box_pack_start (GTK_BOX (box), text_view, FALSE, FALSE, 2);
+
+  /* Drawing area */
+  drawing_area = gtk_drawing_area_new ();
+  gtk_widget_set_size_request (drawing_area, 320, 240);
+  g_object_set (drawing_area, "has-tooltip", TRUE, NULL);
+  g_signal_connect (drawing_area, "expose_event",
+                   G_CALLBACK (drawing_area_expose), NULL);
+  g_signal_connect (drawing_area, "query-tooltip",
+                   G_CALLBACK (query_tooltip_drawing_area_cb), NULL);
+  gtk_box_pack_start (GTK_BOX (box), drawing_area, FALSE, FALSE, 2);
+
+  /* Done! */
+  gtk_widget_show_all (window);
+
+  gtk_main ();
+
+  return 0;
+}